CEEcil: engineering a support “teammate” with human memory

由 Jonathan Devlin
Coinbase Logo

Most "AI agents" are tools. You invoke them, they run, they stop. For our engineering org we wanted something you could @mention in Slack the way you'd ping a coworker: software that remembers what the team talked about yesterday, follows up on a dropped thread, and pushes back when you ask it to do something it shouldn't.

"Teammate" here is a metaphor. CEEcil is a software system. We designed it to show up, retain context, and apply judgment the way a reliable colleague would, and that is the sense in which we use the word.

The individual pieces are familiar. What matters is how they compose. CEEcil answers the way a person does: long-term memory first, recent context second, live lookup only for the facts it still lacks. This post covers that memory architecture, the design trade-offs, and what broke along the way.

The problem: context scattered across time zones

Our engineering org spans opposite sides of the world. The history that would shorten an incident sits with people who are possibly offline for hours.

When something breaks, the engineer who needs that history has two options. Page the person who holds it (potentially off-hours) or wait, context-switch, reverse-engineer what is happening, run ad-hoc queries, and troubleshoot without that history until the person is back. One costs sleep. The other costs hours of the incident.

A 24/7 rotation keeps someone on the pager. The history still walks when people leave, teams reorg, or ownership moves. We wanted an always-on system that retains context across hand-offs, so the on-call starts with that history.

What "teammate" means in practice

That framing drove every design decision. Three principles:

  • Feels like a coworker. No "I'm happy to help!", no five-paragraph essays. Fast, direct answers with sources, in the voice the team actually uses.

  • Carry context without being re-briefed. A good coworker remembers yesterday. That means memory.

  • Say no when the ask is wrong. More on that below.

Cost-tiered cognition: don't reach for the model first

Routing every request through a large language model is slow, expensive, and non-deterministic for questions that don't need it. CEEcil resolves requests through a cost-ordered ladder and only escalates when it has to:

ceecil image 1

The cheap path wins when it can. "Who's on call?" is a direct API lookup with no model in the loop. "What is X?" retrieves from the knowledge base and grounds the answer in what it finds. Only open-ended, multi-step investigations reach the full agent runtime.

The intent-classify step is the hinge. A small model picks a route; what happens next is often fully deterministic. Classifying "who's on call?" as an on-call lookup hands off to an API call that returns the same answer every time. Classifying a question as knowledge hands off to retrieval against a versioned corpus. The model decides which ladder rung, then gets out of the way. Misroutes degrade to the next tier rather than failing hard.

If the knowledge base is stale, the answer can be stale: same failure mode as a human reading an outdated wiki page. We make that visible. Every page carries a last-updated date, and when CEEcil quotes an article it surfaces that date so the reader can judge freshness instead of trusting a timeless-sounding answer. The corpus also lives in git, so every page is reviewable by commit and roll-backable; staleness shows up as PR debt we can see, rather than silent drift inside an opaque index. Recent operational context comes from the memory layer below, which refreshes on a minutes-to-hours cadence, so the system is not solely dependent on the compiled corpus for "what changed today."

This ladder has more moving parts than "send everything to the LLM," and we maintain the routing logic ourselves. For something that runs continuously, the determinism and cost control are worth it. Cost and latency track the difficulty of the question instead of a flat per-request price.

The core idea: give the agent a human memory

Most AI agents treat every interaction as a blank slate. Their useful memory lasts roughly one conversation. You end up re-explaining projects, summarizing blockers, and copy-pasting status updates, playing librarian for a system that forgets you between messages. We wanted out of that loop.

When a person answers a hard question in Slack, they don't start from zero. They lean on institutional knowledge built up over time, recall what was discussed recently, and look things up only for the specifics they still lack. CEEcil uses the same three layers.

Short-term observation. A background worker passively reads a scoped, opt-in set of channels every few minutes and extracts a handful of durable facts from each window: decisions made, blockers raised, ownership, deadlines. It is a read-only observer and never posts from this path. It is configured to ignore and not persist personal or customer-identifying information, keeping only the operational gist. These raw observations are short-lived.

"Dreaming": nightly consolidation. Raw five-minute observations are noisy and overlapping. Once a night, CEEcil runs a reflective pass we call “dreaming.” It distills the day's raw windows into a durable per-channel summary. The point of that pass is curation: the long-term foundation is built on decisions and outcomes, not the messy churn of a live channel. That summary is retained far longer than the raw material it came from.

Retrieval on demand. We do not dump memory into every prompt. Our early attempts followed the industry default: prepend an "everything we know" block to each message. They failed immediately: noise, token cost, diluted focus. A coworker does not re-read the entire company wiki before answering a quick question. CEEcil treats memory as a tool it chooses to use. It pulls what is relevant through a small set of retrieval tools exposed over the Model Context Protocol (MCP): search this channel, search by entity (a service or a ticket) across channels, or get recent activity.

A single answer can combine the dreamed foundation, recent thread context, and a live lookup. Same layering a person does without thinking about it.

Privacy is constrained by construction

An observer that reads team channels is a privacy surface. We constrain it in the architecture, with policy as a second line. Observation is opt-in and scoped: memory is built only from a small set of channels explicitly approved for observation, and it permanently excludes private channels and DMs. Approving a channel later does not retroactively pull in private history. Cross-channel surfacing is gated the same way. The observer is configured to ignore and not persist personal or customer-identifying details, and we periodically review its behavior against our internal privacy and data-handling policies. This is a mitigation we keep under active scrutiny.

Knowledge without a vector database

CEEcil's knowledge base is a corpus of Markdown files with structured front-matter, following Google's Open Knowledge Format (OKF). OKF is a vendor-neutral spec for representing knowledge as a portable directory of Markdown files that both humans and agents can read (v0.1 spec). The corpus is compiled directly into the service binary, so one deploy is one atomic snapshot of what it knows.

That last point is the real reason we skipped a vector database. Our documentation is the knowledge base. Anyone on the team can open a Markdown file, read what the agent knows, edit it in a pull request, and see exactly what will ship. No embeddings pipeline, no opaque index, no separate "agent brain" that drifts from the docs humans maintain. Retrieval is case-insensitive text search with a small ranking boost for title matches. For a corpus of this size that is genuinely good enough, and the whole thing stays auditable and diffable in git.

We know we will outgrow substring search. We wrote down the trigger (a few hundred pages) and we are comfortable waiting for it. CEEcil is meant to be one of many team-scoped agents; a single team's context is a small, bounded corpus. Narrow context is cheaper to search and more accurate than a company-wide index. Until we hit the trigger, the simpler system keeps the team contributing pages instead of waiting on an embeddings project. The same corpus powers both a human-browsable wiki and the agent's retrieval: one source of truth.

Stateless by design

Every time CEEcil is mentioned, it starts a fresh reasoning session and reads the live Slack thread rather than replaying a stored conversation. That sounds odd for something we call a "teammate with memory," but it kills a class of bugs around shared, multi-participant threads and keeps behavior reproducible. Long-term memory lives in the memory layer above. The immediate conversation is always read fresh from the source. The instruction to the agent is blunt:

ceecil code 1

Presence: showing up, not just answering

A tool waits to be invoked. A teammate is around. Several behaviors that changed how people relate to CEEcil have little to do with answering a direct question:

It jumps into threads on its own. In the channels it watches, CEEcil doesn't wait to be @mentioned. When something relevant lands and it has something useful to add, it joins the way a colleague reading along would.

cecil screenshot 1
  • It reacts with emoji. Sometimes the right response is a :+1: or a :eyes:, a lightweight signal that someone is paying attention without derailing the thread.

  • It follows up. If CEEcil is the last message in a thread and nobody has responded, it bumps the thread a couple of hours later: "still need an answer here?"

  • It shows up for the non-work stuff too. Weekly thank-you threads, shout-outs. Being on a team is more than tickets and runbooks.

ceecil 2 screenshot

These are presence behaviors, and presence is what builds trust. When CEEcil is in the trenches day after day (reacting, chiming in, checking back), people stop summoning a bot and start treating it as someone on the team. That trust is what makes the 4:30am hand-off possible. Nobody pages a tool they have to remember exists.

We keep these behaviors on a short leash. Proactive participation is rate-limited and scoped to approved channels, and it is one of the first things our kill switches can disable. When we are unsure whether to chime in, we stay quiet.

Judgment: the moment it felt like a teammate

The clearest signal the framing was working came when someone asked CEEcil to add an operational runbook to its knowledge base. CEEcil noticed the document contained live customer-identifying information and declined to commit it to version control. Committing customer-identifying data to source control is not allowed, and CEEcil reinforced that policy. It offered three compliant options: a sanitized version, a link-only stub pointing to a system approved for customer data, or moving the operational details into a form that carries no customer-identifying information. The "full document in git" option is disallowed, and CEEcil treats it that way.

A tool would have committed the file. CEEcil said customer data doesn't belong in git history and listed the compliant options. We didn't hand-code that specific check. It fell out of giving the agent clear principles and room to apply them.

Under the hood: how it's actually built

Here is the architecture, at the level we can share publicly.

CEEcil is two pieces: a Go service and an agent runtime. Picture the service as the body (ears, eyes, hands) and the runtime as the brain. We split them on purpose. The body is our operational safety rail against hallucinations.

Models invent things when you let them. So the cheap paths never ask a model for a fact. Ask who is on call and the classifier may pick the route, but Go hits the paging API and posts the roster it got back. The brain never gets to invent a name onto the schedule. Ask a wiki question and Go pulls the matching pages first; the answer has to come from that context. If a page was never retrieved, owners and channel names from thin air do not make it into the reply. Slack posts go out through the Go service too. The brain can reason and request tools. It cannot publish into your channel on its own.

  1. The Go service. Owns ingest, routing, memory, the knowledge corpus, workers, state, kill switches, and every Slack reply. This is where the teammate behaviors live. We ship it like any other production service.

  2. The agent runtime. The multi-step reasoner. The service calls it only when a request needs open-ended work. Probabilistic code stays on that side of the fence so the service stays boring and easy to replay.

They talk over the Model Context Protocol (MCP). CEEcil is both an MCP server and an MCP client. The service exposes memory search, knowledge search, live lookups, and Slack reads as tools the brain can call. Same protocol means other agents can reuse those tools, and CEEcil can call other agents the same way. Each tool call crosses the boundary, so we log the tool name, whether it succeeded, and how long it took. That alone has saved us more debugging time than almost anything else in the stack.

A request, end to end

ceecil 2 code

A few properties:

  • Ack fast, work async. Slack wants an ack in a few seconds; a full agent run can take much longer. Ingest acks immediately, work runs in a background goroutine, and the reply posts when ready. Events de-dupe on source id so a Slack retry cannot double-answer.

  • State lives in Postgres. Open threads, audit rows, and memory tables are ordinary relational data. No vector database. No hidden per-conversation server state. If it is durable, it is a row you can query.

  • Background workers do the proactive work. Memory observation, nightly dreaming, and follow-up bumps are scheduled in the service. The model does not decide to be proactive, which keeps that behavior predictable and rate-limited.

  • Two model tiers. A small, cheap model handles high-frequency work (intent classification, observation extraction). A stronger model handles the rare, expensive work (nightly consolidation, multi-step reasoning). Most of the cost story is just matching model size to how often the job runs.

Guardrails, because it writes things

An agent that can open pull requests and post to Slack needs brakes:

  • Kill switches. Live config can disable the agent, the workers, or write actions without a redeploy. Something looks wrong, we cut it in seconds.

  • Audit what we can see. For each reply we store structural metadata: channel, routing path, tool names, latency, cost. Reply text lives in the agent-runtime run record, linked by run id. There is no outbound toxicity classifier today. If CEEcil starts posting garbage, a human in the channel usually notices first; ops dashboards show volume and failures; the kill switch stops new posts. We also track cost, latency, and routing-tier mix so we tune against real traffic.

  • Spend limits. Every model call has caps per invocation, per thread, and in aggregate. A runaway loop hits a ceiling before it hits a surprise bill.

  • Humans merge. CEEcil can draft and open a pull request. It cannot merge its own work, and it says so when it hands back the steps only a person can take.

Copy the boring parts. Kill switches, spend caps, idempotency, and an audit trail you can actually query beat a clever prompt.

Results

The headline result is availability. Questions that used to wait hours for an offline expert now get a grounded first answer while that person is still asleep. On the deterministic paths, a skill or a wiki-backed answer comes back in a second or two. Even the full-agent path, which does real multi-step work, typically responds in a couple of minutes, inside the response-time target we hold it to. This availability and strong knowledge base have already paid dividends. CEEcil was able to triage and mitigate a low severity production incident via its ability to provide meaningful context + analysis.

The traffic mix is more interesting than a clean number. Today, most requests are open-ended enough to route to the full agent runtime, and only a minority resolve on the cheap deterministic paths. That is the opposite of the ratio our cost-tiered design assumes at steady state. Two reads: people trust CEEcil with hard questions, and the cheap-path share should grow as the knowledge base fills in and more questions match a skill or a wiki answer. Even with that agent-heavy mix, running CEEcil continuously stays inexpensive, in the same range as other internal tools we operate, and well below what a model-per-request design would spend. CEEcil has also started closing the loop on its own work: it fixed a bug in its own memory digest, wrote the tests, and opened the pull request for a human to review and merge.

The trade-offs we chose on purpose

This is not a finished system. Most of our design decisions bought one property at the direct expense of another. In each case we know the signal that would make us change course.

Simplicity over recall. Substring search on a corpus compiled into the binary trades a recall ceiling for fit and approachability. A single team's context is a small, bounded knowledge base: cheaper and more accurate than a company-wide index. A plain directory of Markdown with simple search means anyone can read the corpus, add a page, or hack on retrieval without a vector database, which turned CEEcil into a shared project the whole team contributes to. We wrote down the corpus-size trigger that would flip us to embeddings if a domain ever outgrows it.

Reproducibility over continuity. Reading the live thread fresh on every mention eliminated a class of shared-thread bugs and made behavior easy to reason about, at the cost of in-session conversational continuity. That is the right call while trust is still being earned. Durable context lives in the memory layer, not in a fragile session state.

Determinism over convenience. A hand-maintained routing ladder is more code to own than "send everything to the model," and the classifier will occasionally pick the wrong tier. We accept that maintenance burden because it keeps the common path fast, cheap, and predictable, and a misroute degrades to the next tier.

Boundaries over velocity. Splitting the deterministic body from the reasoning brain gives us sandboxing and clean interfaces, but some changes touch two moving parts instead of one. For a system that writes to Slack and opens pull requests, we take the boundary every time.

Usefulness against a privacy surface. A passive observer that reads team channels is the single thing we scrutinize most. Constraining it by construction (approved channels only, private history permanently excluded, no personal or customer identifiers stored) is a mitigation we treat as an ongoing obligation.

Closing thought

What moved CEEcil from tool to teammate was architecture: a durable memory the agent consolidates over time, recent recall, and targeted live lookup, layered the way people actually think. Get that structure right and a competent model starts to feel like a colleague.

CEEcil is one team's teammate today. The pattern we think generalizes: narrow-domain agents that own their knowledge, remember their context, and can call one another. The goal is concrete. The 4:30am page gets triaged, routed, and when the fix is clear enough a pull request is waiting for the on-call engineer to review before anyone has to wake up.



最新报道

Disclaimers: Derivatives trading through the Coinbase Advanced platform is offered to eligible EEA customers by Coinbase Financial Services Europe Ltd. (CySEC License 374/19). In order to access derivatives, customers will need to pass through our standard assessment checks to determine their eligibility and suitability for this product.